package com.venus.config.shiro;

import com.venus.common.Commons;
import com.venus.entity.User;
import com.venus.service.BaseService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * Created by admin on 13/10/17.
 */
@Configuration
public class ShiroConfig {

	private static final Logger LOGGER = LoggerFactory.getLogger(ShiroConfig.class);

	@Resource
	private BaseService baseService;

	@Bean
	public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * ShiroFilterFactoryBean 处理拦截资源文件问题。
	 * 注意：单独一个ShiroFilterFactoryBean配置是或报错的，因为在
	 * 初始化ShiroFilterFactoryBean的时候需要注入：SecurityManager
	 *
	 Filter Chain定义说明
	 1、一个URL可以配置多个Filter，使用逗号分隔
	 2、当设置多个过滤器时，全部验证通过，才视为通过
	 3、部分过滤器可指定参数，如perms，roles
	 *
	 */
	@Bean
	public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
		LOGGER.info("ShiroConfiguration.shirFilter()");
		ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();
		// 必须设置 SecurityManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
		shiroFilterFactoryBean.setLoginUrl("/base/login");
		// 登录成功后要跳转的链接
		//shiroFilterFactoryBean.setSuccessUrl("/base/mainPage");
		//未授权界面;
		shiroFilterFactoryBean.setUnauthorizedUrl("/base/noPermission");
		//拦截器
		Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
		//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
		filterChainDefinitionMap.put("/logout", "logout");
		//静态资源配置--开始
		filterChainDefinitionMap.put("/avatar/**","anon");
		filterChainDefinitionMap.put("/css/**","anon");
		filterChainDefinitionMap.put("/images/**","anon");
		filterChainDefinitionMap.put("/js/**","anon");
		filterChainDefinitionMap.put("/layui/**","anon");
		//静态资源配置--结束
		//监控、文档类 -- 开始
		filterChainDefinitionMap.put("/druid/**","anon");
		filterChainDefinitionMap.put("/swagger-ui.html","anon");
		filterChainDefinitionMap.put("/swagger-resources/**","anon");
		filterChainDefinitionMap.put("/webjars/**","anon");
		filterChainDefinitionMap.put("/v2/**","anon");
		//监控、文档类 -- 结束
		filterChainDefinitionMap.put("/base/home","authc");
		filterChainDefinitionMap.put("/base/**","anon");
		filterChainDefinitionMap.put("/rest/**","anon");
		//<!-- 过滤链定义，从上向下顺序执行，一般将/**放在最为下边 -->:这是一个坑呢，一不小心代码就不好使了;
		//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
		filterChainDefinitionMap.put("/**", "authc");
		//filterChainDefinitionMap.put("/**", "anon");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}


	@Bean
	public SecurityManager securityManager(){
		DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
		//设置realm.
		MyRealm myRealm = new MyRealm();
		//设置md5加密
		myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
		securityManager.setRealm(myRealm);
		// 自定义缓存实现
		securityManager.setCacheManager(cacheManager());
		// 自定义session管理 使用redis
		securityManager.setSessionManager(sessionManager());
		return securityManager;
	}

	/**
	 * 凭证匹配器 （由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
	 * 所以我们需要修改下doGetAuthenticationInfo中的代码; @return
	 */
	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher() {
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法;
		hashedCredentialsMatcher.setHashIterations(1);// 散列的次数，比如散列两次，相当于md5(md5(""));
		//hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);//表示是否存储散列后的密码为16进制，需要和生成密码时的一样，默认是base64；
		return hashedCredentialsMatcher;
	}

	/**
	 *  开启shiro aop注解支持.
	 *  使用代理方式;所以需要开启代码支持;
	 * @param securityManager
	 * @return
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

	/**
	 * cacheManager 缓存
	 * @return
	 */
	public CacheManager cacheManager() {
		CacheManager cacheManager = new MemoryConstrainedCacheManager();
		return cacheManager;
	}

	/**
	 * shiro session的管理
	 */
	@Bean
	public DefaultWebSessionManager sessionManager() {
		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		sessionManager.setGlobalSessionTimeout(1800000); //单位毫秒
		return sessionManager;
	}

	private class MyRealm extends AuthorizingRealm {

		/**
		 * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用,负责在应用程序中决定用户的访问控制的方法
		 */
		@Override
		protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
			SimpleAuthorizationInfo info = null;
			User user = (User) SecurityUtils.getSubject().getSession().getAttribute(Commons.SESSION_USER_KEY);
			if(null  != user) {
				info= new SimpleAuthorizationInfo();
				for(String role : user.getRoles()) {
					info.addRole(role);
				}
				Set<String> list = (Set<String>)user.getPermissions();
				info.addStringPermissions(list);
			}
			LOGGER.debug("===================================================================");
			LOGGER.debug("User:" + user + "，info.getRole:" + info.getRoles() + ", StringPermissions:" + info.getStringPermissions());
			LOGGER.debug("===================================================================");
			return info;
		}

		/**
		 * 认证回调函数，登录信息和用户验证信息验证
		 */
		@Override
		protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
			// 验证用户是否可以登录
			User user = baseService.findByUsername(tokenToUser((UsernamePasswordToken) authcToken));
			if(null == user) {
				throw new UnknownAccountException();
			}
			//Set<String> authList = baseService.findUserAuthList(user.getId());
			user.setPermissions(baseService.findPermissionsByUserId(user.getId()));
			user.setRoles(baseService.findByRolesUserId(user.getId()));
			// 设置session
			Session session = SecurityUtils.getSubject().getSession();
			session.setAttribute(Commons.SESSION_USER_KEY, user);
			//当前 Realm 的 name
			String realmName = this.getName();
			//登陆的主要信息: 可以是一个实体类的对象, 但该实体类的对象一定是根据 token 的 username 查询得到的.
			Object principal = authcToken.getPrincipal();
			LOGGER.debug("===================================================================");
			LOGGER.debug("session:" + session.getAttribute(Commons.SESSION_USER_KEY) );
			LOGGER.debug("===================================================================");
			return new SimpleAuthenticationInfo(principal, user.getPassword(), realmName);
		}

		/**
		 * 转换 AuthenticationToken
		 * @param authcToken
		 * @return
		 */
		private String tokenToUser(UsernamePasswordToken authcToken) {
			return authcToken.getUsername();
		}
	}
}
